Sprint 3 — News inbox#76
Conversation
Goal Packet v2.0 format. Single-mission contract for Codex. Mission: build a /news route that surfaces the worker-backed news feed (getNews / markNewsRead) the audit found unwired. Includes filter UI, mark-read on view, Sidebar nav entry, and an unread badge in TopBar. Read-first, allowed-write, protected scope, non-negotiables, milestone loop, validation loop, evaluator-visible proof, pause conditions, done criteria, and final report all encoded inline so the slash command can stay thin. Builds on Sprint 1 cleanup (PR #74) and Sprint 2 revised onboarding canonicalization (PR #75), both merged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The audit found getNews(limit?) and markNewsRead(newsId) exposed in useWorker() and powered by sim-core/narrative/newsFeed.ts, but no UI consumed them. SettingsPage showed the unread count, full stop. This adds: - /news route lazy-loaded under AppLayout, wrapped in RouteErrorBoundary like every other feature route. - NewsPage list rendering: headline, body excerpt, category badge, priority indicator, optional tag chip, timestamp (Season X · Day Y or Now), related team chips. Read items have a clear visual treatment. - Filters: All / Unread toggle plus a category multi-select chip group. Filtering happens client-side over the worker's getNews(100) result. - Mark-read on item open: calls markNewsRead(id), persists the active save through the existing IndexedDB save path, decrements the in-session unread count. - Mobile-survivable at 375x667 — no horizontal overflow. - Loading skeleton, empty-state panel, and error toast for worker failures. - A small newsEvents helper module dispatches a "news-read" event so the TopBar unread chip can react across components without prop threading. Tests: NewsPage.test.tsx (4 tests) covers list render, filter behavior, mark-read flow, and worker mock surface. Schema stays at v33. No sim-core or contracts edits. Co-Authored-By: Codex GPT-5 <noreply@openai.com> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Sidebar: new NavItem { to: '/news', label: 'News', icon: <Inbox /> }.
Newspaper icon stays with Press Room. Sidebar.test asserts the entry
renders.
- TopBar: new unread chip that subscribes to news-read events and
refetches worker getNews() to recompute the count after every read.
Decrements as the user reads items. Match the existing TopBar density
(small chip, no emoji, lucide-only).
A new TopBar.test.tsx covers the chip render, the count reflecting the
worker mock, and the post-read recompute.
Co-Authored-By: Codex GPT-5 <noreply@openai.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…creenshots
Captures the milestone-by-milestone audit trail Codex produced during
Sprint 3, the final Sprint-3-complete status report (reframed to
distinguish the news inbox feature — which fully shipped — from the
hard-reload state-hydration issue, which is pre-existing app-wide
behavior that affects every in-game route and is now queued as Sprint
3.5), and six browser-smoke screenshots:
- 01-dashboard-after-month.png Day 31 save context
- 02-news-inbox-unread.png /MBD/news list with worker news
- 03-news-category-filter.png Trade-category filter applied
- 04-news-item-read.png Read state + TopBar chip decrement
- 05-news-mobile-375.png 375x667 viewport, no overflow
- 06-news-hard-reload-blocked.png pre-existing app-wide behavior
(documented, not a Sprint 3 regression)
IndexedDB save-state proof from save-slot-2: unread 580 -> 579 after
read and survives reload at the data layer (only the routing redirect
to Save Hub masks it visually).
Co-Authored-By: Codex GPT-5 <noreply@openai.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3c64caa8a2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| const unreadCount = items.filter((item) => !item.read).length; | ||
| const filteredItems = useMemo(() => items.filter((item) => { | ||
| const readMatch = readFilter === 'all' || !item.read; |
There was a problem hiding this comment.
Keep opened items visible under the Unread filter
When the user has selected the Unread filter, opening any unread story immediately calls markItemRead, which optimistically flips read to true; this predicate then excludes that same item from filteredItems. In that scenario the card disappears as soon as the user tries to view it, so they cannot read the expanded body from the Unread inbox.
Useful? React with 👍 / 👎.
| const nextNews = await worker.getNews(NEWS_LIMIT); | ||
| setItems([...(nextNews ?? [])].sort(compareNewsForInbox)); |
There was a problem hiding this comment.
Load more than unread stories for the All filter
This fetch uses worker.getNews, but the worker implementation returns only getUnreadNews(requireState().news).slice(...) (apps/web/src/workers/sim.worker.queries.ts:2666-2668). As a result, after a refresh or revisit, the All filter and category counts can never include already-read stories, so read items effectively vanish from the inbox instead of remaining available under All.
Useful? React with 👍 / 👎.
Goal Packet v2.0 format. Single-mission contract for Codex. Mission: persist useGameStore active-save metadata to localStorage via Zustand persist middleware, then auto-load the active save on app boot before AppLayout's isInitialized guard fires. Hard reload at any in-game route renders that route instead of redirecting to Save Hub. Read-first, allowed-write, protected scope, non-negotiables, milestone loop, validation loop, evaluator-visible proof, pause conditions, done criteria, and final report all encoded inline. Built on top of Sprint 3 (PR #76); rebase onto main after that merges. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Goal Packet v2.0 format. Single-mission contract for Codex. Mission: persist useGameStore active-save metadata to localStorage via Zustand persist middleware, then auto-load the active save on app boot before AppLayout's isInitialized guard fires. Hard reload at any in-game route renders that route instead of redirecting to Save Hub. Read-first, allowed-write, protected scope, non-negotiables, milestone loop, validation loop, evaluator-visible proof, pause conditions, done criteria, and final report all encoded inline. Built on top of Sprint 3 (PR #76); rebase onto main after that merges. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs(goal): replace Sprint 3 GOAL.md with Sprint 3.5 mission contract Goal Packet v2.0 format. Single-mission contract for Codex. Mission: persist useGameStore active-save metadata to localStorage via Zustand persist middleware, then auto-load the active save on app boot before AppLayout's isInitialized guard fires. Hard reload at any in-game route renders that route instead of redirecting to Save Hub. Read-first, allowed-write, protected scope, non-negotiables, milestone loop, validation loop, evaluator-visible proof, pause conditions, done criteria, and final report all encoded inline. Built on top of Sprint 3 (PR #76); rebase onto main after that merges. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(app): auto-resume active save on hard reload Co-Authored-By: Codex GPT-5 <noreply@openai.com> --------- Co-authored-by: Claude Code <noreply@anthropic.com> Co-authored-by: Codex GPT-5 <noreply@openai.com>
Summary
Sprint 3 ships the News inbox the audit found unwired:
getNews(limit?)andmarkNewsRead(newsId)were exposed inuseWorker()and powered bysim-core/narrative/newsFeed.ts, but no UI consumed them./newsroute, lazy-loaded under AppLayout. Renders workerNewsItemobjects newest-first with headline, body excerpt, category badge, priority indicator, optional tag chip, timestamp, related team chips. Mobile-survivable at 375×667.getNews(100)result.markNewsRead(id)and persists through the existing IndexedDB save path. IDB proof: unread580 → 579after read, survives reload at the data layer.Inboxicon.Newspaperstays with Press Room.news-readevent so the count refreshes after any read across the app.Schema stays at v33. No new deps. No sim-core, contracts, or worker edits. Bundle: new lazy chunk at 10.08 KB raw / 3.39 KB gzip, all other chunks unchanged,
bundleBudget.test.tspasses.Verification
Browser smoke (screenshots under
apps/web/docs/screenshots/sprint-3/):Trade)On the hard-reload Done When item
The GOAL.md included a Done When item: "
/MBD/newshard-reload survives Sprint 2's BrowserRouter basename (should be free)". That claim was wrong.Sprint 2's BrowserRouter basename fix solved URL parsing —
/MBD/newsnow correctly resolves to the/newsroute table entry. It did NOT solve state hydration.AppLayout:446hasif (!isInitialized) return <Navigate to="/" replace />, anduseGameStoreis a plain Zustand store with no persistence middleware, soisInitializedresets to false on every hard reload. This redirect fires on every in-game route —/dashboard,/roster,/trade,/news— not just news.Codex correctly paused at this Done When item because the fix requires modifying protected files (
useGameStore,AppLayout, orApp). Sprint 3 ships the news inbox feature complete; the auto-resume-on-reload polish is its own sprint (Sprint 3.5 in the queue) that affects the entire app shell.The IDB evidence confirms the data layer is correct:
markNewsRead → savewrites through and read state survives the reload. Only the routing-to-Save-Hub redirect masks it visually.How the work was sliced
4 commits on
goal/sprint-3-news-inbox:docs(goal):— Sprint 3 mission contractfeat(news):— NewsPage feature, route, libfeat(layout):— Sidebar nav entry + TopBar unread chipdocs(news):— STATUS.md +.logs/goal-progress.md+ 6 browser-smoke screenshotsProcess
Lineage
Test plan
🤖 Generated with Claude Code (review/ops) and Codex GPT-5 (build).